//===--- CommentConversion.cpp - Conversion of comments to other formats --===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/ide/CommentConversion.h"
#include "polarphp/ast/AstContext.h"
#include "polarphp/ast/Comment.h"
#include "polarphp/ast/Decl.h"
#include "polarphp/ast/USRGeneration.h"
#include "polarphp/ast/RawComment.h"
#include "polarphp/basic/SourceMgr.h"
#include "polarphp/markup/Markup.h"
#include "polarphp/markup/XMLUtils.h"
#include "polarphp/llparser/Token.h"
#include "polarphp/global/Subsystems.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/Index/CommentToXML.h"

using namespace polar::markup;
using namespace polar;
using namespace llparser;

//===----------------------------------------------------------------------===//
// Conversion to XML.
//===----------------------------------------------------------------------===//

namespace {
struct CommentToXMLConverter {
   raw_ostream &OS;

   CommentToXMLConverter(raw_ostream &OS) : OS(OS) {}

   void printRawHTML(StringRef Tag) {
      OS << "<rawHTML>";
      appendWithCDATAEscaping(OS, Tag);
      OS << "</rawHTML>";
   }

   void printASTNode(const MarkupAstNode *N) {
      switch (N->getKind()) {
#define MARKUP_AST_NODE(Id, Parent) \
    case AstNodeKind::Id: \
      print##Id(cast<Id>(N)); \
      break;
#define ABSTRACT_MARKUP_AST_NODE(Id, Parent)
#define MARKUP_AST_NODE_RANGE(Id, FirstId, LastId)
#include "polarphp/markup/AstNodesDefs.h"
      }
   }

#define MARKUP_SIMPLE_FIELD(Id, Keyword, XMLKind) \
  void print##Id(const Id *Field) { \
    OS << "<" #XMLKind << ">"; \
    for (auto Child : Field->getChildren()) \
      printASTNode(Child); \
\
    OS << "</" #XMLKind << ">"; \
}
#include "polarphp/markup/SimpleFieldsDefs.h"

   void printDocument(const Document *D) {
      llvm_unreachable("Can't print a polar::markup::Document as XML directly");
   }

   void printBlockQuote(const BlockQuote *BQ) {
      for (const auto *N : BQ->getChildren())
         printASTNode(N);
   }

   void printList(const List *L) {
      OS << (L->isOrdered() ? "<List-Number>" : "<List-Bullet>");
      for (const auto *N : L->getChildren())
         printASTNode(N);

      OS << (L->isOrdered() ? "</List-Number>" : "</List-Bullet>");
   }

   void printItem(const Item *I) {
      OS << "<Item>";
      for (const auto *N : I->getChildren())
         printASTNode(N);

      OS << "</Item>";
   }

   void printCode(const Code *C) {
      OS << "<codeVoice>";
      appendWithXMLEscaping(OS, C->getLiteralContent());
      OS << "</codeVoice>";
   }

   void printCodeBlock(const CodeBlock *CB) {
      OS << "<CodeListing language=\"";
      appendWithXMLEscaping(OS, CB->getLanguage());
      OS << "\">";
      SmallVector<StringRef, 16> CodeLines;
      CB->getLiteralContent().split(CodeLines, "\n");
      for (auto Line : CodeLines) {
         OS << "<zCodeLineNumbered>";
         appendWithCDATAEscaping(OS, Line);
         OS << "</zCodeLineNumbered>";
      }
      OS << "</CodeListing>";
   }

   void printParagraph(const Paragraph *P) {
      OS << "<Para>";
      for (const auto *N : P->getChildren())
         printASTNode(N);

      OS << "</Para>";
   }

   void printHeader(const Header *H) {
      llvm::SmallString<4> Tag;
      llvm::raw_svector_ostream TagStream(Tag);
      TagStream << "<h" << H->getLevel() << ">";
      printRawHTML(TagStream.str());
      for (auto Child : H->getChildren())
         printASTNode(Child);

      llvm::SmallString<5> EndTag;
      llvm::raw_svector_ostream EndTagStream(EndTag);
      EndTagStream << "</h" << H->getLevel() << ">";
      printRawHTML(EndTagStream.str());
   }

   void printHRule(const HRule *HR) {
      printRawHTML("<hr/>");
   }

   void printText(const Text *T) {
      appendWithXMLEscaping(OS, T->getLiteralContent());
   }

   void printHTML(const HTML *H) {
      printRawHTML(H->getLiteralContent());
   }

   void printInlineHTML(const InlineHTML *IH) {
      printRawHTML(IH->getLiteralContent());
   }

   void printEmphasis(const Emphasis *E) {
      OS << "<emphasis>";
      for (const auto *IC : E->getChildren())
         printASTNode(IC);

      OS << "</emphasis>";
   }

   void printStrong(const Strong *S) {
      OS << "<bold>";
      for (const auto *N : S->getChildren())
         printASTNode(N);

      OS << "</bold>";
   }

   void printLink(const Link *L) {
      SmallString<32> Tag;
      llvm::raw_svector_ostream S(Tag);
      S << "<Link href=\"";
      appendWithXMLEscaping(S, L->getDestination());
      S << "\">";

      OS << S.str();

      for (const auto N : L->getChildren())
         printASTNode(N);

      OS << "</Link>";
   }

   void printSoftBreak(const SoftBreak *SB) {
      OS << " ";
   }

   void printLineBreak(const LineBreak *LB) {
      printRawHTML("<br/>");
   }

   void printPrivateExtension(const PrivateExtension *PE) {
      llvm_unreachable("Can't directly print a Swift Markup PrivateExtension");
   }

   void printImage(const Image *I) {
      SmallString<64> Tag;
      llvm::raw_svector_ostream S(Tag);
      S << "<img src=\"" << I->getDestination() << "\"";
      if (I->hasTitle())
         S << " title=\"" << I->getTitle() << "\"";
      if (I->getChildren().size()) {
         S << " alt=\"";
         for (const auto N : I->getChildren())
            printInlinesUnder(N, S);
         S << "\"";
      }
      S << "/>";
      printRawHTML(S.str());
   }

   void printParamField(const ParamField *PF) {
      OS << "<Parameter>";
      OS << "<Name>";
      OS << PF->getName();
      OS << "</Name>";
      OS << "<Direction isExplicit=\"0\">in</Direction>";

      if (PF->isClosureParameter()) {
         OS << "<ClosureParameter>";
         visitCommentParts(PF->getParts().getValue());
         OS << "</ClosureParameter>";
      } else {
         OS << "<Discussion>";
         for (auto Child : PF->getChildren()) {
            printASTNode(Child);
         }
         OS << "</Discussion>";
      }
      OS << "</Parameter>";
   }

   void printResultDiscussion(const ReturnsField *RF) {
      OS << "<ResultDiscussion>";
      for (auto Child : RF->getChildren())
         printASTNode(Child);
      OS << "</ResultDiscussion>";
   }

   void printThrowsDiscussion(const ThrowsField *RF) {
      OS << "<ThrowsDiscussion>";
      for (auto Child : RF->getChildren())
         printASTNode(Child);
      OS << "</ThrowsDiscussion>";
   }

   void printTagFields(ArrayRef<StringRef> Tags) {
      OS << "<Tags>";
      for (const auto Tag : Tags) {
         if (Tag.empty()) {
            continue;
         }
         OS << "<Tag>";
         appendWithXMLEscaping(OS, Tag);
         OS << "</Tag>";
      }
      OS << "</Tags>";
   }

   void visitDocComment(const DocComment *DC);
   void visitCommentParts(const polar::markup::CommentParts &Parts);
};
} // unnamed namespace

void CommentToXMLConverter::visitCommentParts(const polar::markup::CommentParts &Parts) {
   if (Parts.brief.hasValue()) {
      OS << "<Abstract>";
      printASTNode(Parts.brief.getValue());
      OS << "</Abstract>";
   }

   if (!Parts.paramFields.empty()) {
      OS << "<Parameters>";
      for (const auto *PF : Parts.paramFields)
         printParamField(PF);

      OS << "</Parameters>";
   }

   if (Parts.returnsField.hasValue())
      printResultDiscussion(Parts.returnsField.getValue());

   if (Parts.throwsField.hasValue())
      printThrowsDiscussion(Parts.throwsField.getValue());

   if (!Parts.tags.empty()) {
      printTagFields(llvm::makeArrayRef(Parts.tags.begin(), Parts.tags.end()));
   }

   if (!Parts.bodyNodes.empty()) {
      OS << "<Discussion>";
      for (const auto *N : Parts.bodyNodes)
         printASTNode(N);

      OS << "</Discussion>";
   }
}

void CommentToXMLConverter::visitDocComment(const DocComment *DC) {
   const Decl *D = DC->getDecl();

   StringRef RootEndTag;
   if (isa<AbstractFunctionDecl>(D)) {
      OS << "<Function";
      RootEndTag = "</Function>";
   } else if (isa<StructDecl>(D) || isa<ClassDecl>(D) || isa<InterfaceDecl>(D)) {
      OS << "<Class";
      RootEndTag = "</Class>";
   } else {
      OS << "<Other";
      RootEndTag = "</Other>";
   }

   {
      // Print line and column number.
      auto Loc = D->getLoc();
      if (Loc.isValid()) {
         const auto &SM = D->getAstContext().SourceMgr;
         StringRef FileName = SM.getDisplayNameForLoc(Loc);
         auto LineAndColumn = SM.getLineAndColumn(Loc);
         OS << " file=\"";
         appendWithXMLEscaping(OS, FileName);
         OS << "\"";
         OS << " line=\"" << LineAndColumn.first << "\" column=\""
            << LineAndColumn.second << "\"";
      }
   }

   // Finish the root tag.
   OS << ">";

   auto *VD = dyn_cast<ValueDecl>(D);

   OS << "<Name>";
   if (VD && VD->hasName()) {
      llvm::SmallString<64> SS;
      llvm::raw_svector_ostream NameOS(SS);
      NameOS << VD->getFullName();
      appendWithXMLEscaping(OS, NameOS.str());
   }
   OS << "</Name>";

   if (VD) {
      llvm::SmallString<64> SS;
      bool Failed;
      {
         llvm::raw_svector_ostream OS(SS);
         Failed = ide::printValueDeclUSR(VD, OS);
      }
      if (!Failed && !SS.empty()) {
         OS << "<USR>" << SS << "</USR>";
      }
   }

   {
      PrintOptions PO = PrintOptions::printInterface();
      PO.PrintAccess = false;
      PO.AccessFilter = AccessLevel::Private;
      PO.PrintDocumentationComments = false;
      PO.TypeDefinitions = false;
      PO.VarInitializers = false;
      PO.ShouldQualifyNestedDeclarations =
         PrintOptions::QualifyNestedDeclarations::TypesOnly;

      OS << "<Declaration>";
      llvm::SmallString<32> DeclSS;
      {
         llvm::raw_svector_ostream DeclOS(DeclSS);
         D->print(DeclOS, PO);
      }
      appendWithXMLEscaping(OS, DeclSS);
      OS << "</Declaration>";
   }

   OS << "<CommentParts>";
   visitCommentParts(DC->getParts());
   OS << "</CommentParts>";

   OS << RootEndTag;
}

static bool getClangDocumentationCommentAsXML(const clang::Decl *D,
                                              raw_ostream &OS) {
   const auto &ClangContext = D->getASTContext();
   const clang::comments::FullComment *FC =
      ClangContext.getCommentForDecl(D, /*PP=*/nullptr);
   if (!FC)
      return false;

   // FIXME: hang the converter object somewhere so that it is persistent
   // between requests to this AST.
   clang::index::CommentToXMLConverter Converter;

   llvm::SmallString<1024> XML;
   Converter.convertCommentToXML(FC, XML, ClangContext);
   OS << XML;
   return true;
}

static void replaceObjcDeclarationsWithSwiftOnes(const Decl *D,
                                                 StringRef Doc,
                                                 raw_ostream &OS) {
   StringRef Open = "<Declaration>";
   StringRef Close = "</Declaration>";
   PrintOptions Options = PrintOptions::printQuickHelpDeclaration();
   std::string S;
   llvm::raw_string_ostream SS(S);
   D->print(SS, Options);
   auto OI = Doc.find(Open);
   auto CI = Doc.find(Close);
   if (StringRef::npos != OI && StringRef::npos != CI && CI > OI) {
      OS << Doc.substr(0, OI) << Open;
      appendWithXMLEscaping(OS, SS.str());
      OS << Close << Doc.substr(CI + Close.size());
   } else {
      OS << Doc;
   }
}

static LineList getLineListFromComment(SourceManager &SourceMgr,
                                       polar::markup::MarkupContext &MC,
                                       const StringRef Text) {
   LangOptions LangOpts;
   auto Tokens = polar::llparser::tokenize(LangOpts, SourceMgr,
                                 SourceMgr.addMemBufferCopy(Text));
   std::vector<SingleRawComment> Comments;
   Comments.reserve(Tokens.size());
   for (auto &Tok : Tokens) {
      if (Tok.is(tok::comment)) {
         Comments.push_back(SingleRawComment(Tok.getText(), 0));
      }
   }
   if (Comments.empty())
      return {};

   RawComment Comment(Comments);
   return MC.getLineList(Comment);
}

std::string ide::extractPlainTextFromComment(const StringRef Text) {
   SourceManager SourceMgr;
   polar::markup::MarkupContext MC;
   return getLineListFromComment(SourceMgr, MC, Text).str();
}

bool ide::getDocumentationCommentAsXML(const Decl *D, raw_ostream &OS) {
   auto MaybeClangNode = D->getClangNode();
   if (MaybeClangNode) {
      if (auto *CD = MaybeClangNode.getAsDecl()) {
         std::string S;
         llvm::raw_string_ostream SS(S);
         if (getClangDocumentationCommentAsXML(CD, SS)) {
            replaceObjcDeclarationsWithSwiftOnes(D, SS.str(), OS);
            return true;
         }
      }
      return false;
   }

   polar::markup::MarkupContext MC;
   auto DC = getCascadingDocComment(MC, D);
   if (!DC)
      return false;

   CommentToXMLConverter Converter(OS);
   Converter.visitDocComment(DC);

   OS.flush();
   return true;
}

bool ide::getLocalizationKey(const Decl *D, raw_ostream &OS) {
   polar::markup::MarkupContext MC;
   auto DC = getCascadingDocComment(MC, D);
   if (!DC)
      return false;

   if (const auto LKF = DC->getLocalizationKeyField()) {
      printInlinesUnder(LKF.getValue(), OS);
      return true;
   }

   return false;
}

bool ide::convertMarkupToXML(StringRef Text, raw_ostream &OS) {
   std::string Comment;
   {
      llvm::raw_string_ostream OS(Comment);
      OS << "/**\n" << Text << "\n" << "*/";
   }
   SourceManager SourceMgr;
   MarkupContext MC;
   LineList LL = getLineListFromComment(SourceMgr, MC, Comment);
   if (auto *Doc = polar::markup::parseDocument(MC, LL)) {
      CommentToXMLConverter Converter(OS);
      Converter.visitCommentParts(extractCommentParts(MC, Doc));
      OS.flush();
      return false;
   }
   return true;
}

//===----------------------------------------------------------------------===//
// Conversion to Doxygen.
//===----------------------------------------------------------------------===//

class DoxygenConverter : public MarkupAstVisitor<DoxygenConverter> {
   llvm::raw_ostream &OS;
   unsigned Indent;
   unsigned IsFreshLine : 1;
   unsigned IsEmptyComment : 1;

   void printIndent() {
      for (unsigned i = 0; i < Indent; ++i) {
         OS << ' ';
      }
   }

   void indent(unsigned Amount = 2) {
      Indent += Amount;
   }

   void dedent(unsigned Amount = 2) {
      Indent -= Amount;
   }

   void print(StringRef Str) {
      for (auto c : Str) {
         if (c == '\n') {
            if (IsFreshLine && !IsEmptyComment)
               OS << "///";
            IsFreshLine = true;
         } else {
            if (IsFreshLine && !IsEmptyComment)
               OS << "///";
            if (IsFreshLine) {
               printIndent();
               IsFreshLine = false;
            }
         }
         OS << c;
         IsEmptyComment = false;
      }
   }

   void printNestedParamField(const ParamField *PF) {
      auto Parts = PF->getParts().getValue();
      if (Parts.brief.hasValue()) {
         visit(Parts.brief.getValue());
      }

      if (!Parts.paramFields.empty()) {
         printNewline();
         print("\\a ");
         print(PF->getName());
         print(" parameters:");
         printNewline();

         print("<ul>");
         printNewline();
         for (auto Param : Parts.paramFields) {
            print("<li>");
            printNewline();
            print(Param->getName());
            print(": ");
            printNestedParamField(Param);
            print("</li>");
            printNewline();
         }
         print("</ul>");
         printNewline();
         printNewline();
      }

      if (Parts.returnsField.hasValue()) {
         printNewline();
         print("\\a ");
         print(PF->getName());
         print(" returns: ");

         for (auto Child : Parts.returnsField.getValue()->getChildren()) {
            visit(Child);
         }
      }

      if (Parts.throwsField.hasValue()) {
         printNewline();
         print("\\a ");
         print(PF->getName());
         print(" error: ");

         for (auto Child : Parts.throwsField.getValue()->getChildren()) {
            visit(Child);
         }
      }

      for (auto BodyNode : Parts.bodyNodes) {
         visit(BodyNode);
      }
   }

public:
   DoxygenConverter(llvm::raw_ostream &OS)
      : OS(OS), Indent(1), IsFreshLine(true), IsEmptyComment(true) {
      printOpeningComment();
   }

   void printNewline() {
      print("\n");
   }

   void printOpeningComment() {
      OS << "///";
   }

   void printUncommentedNewline() {
      OS << '\n';
   }

   void visitDocument(const Document *D) {
      for (const auto *Child : D->getChildren())
         visit(Child);
   }

   void visitBlockQuote(const BlockQuote *BQ) {
      print("<blockquote>");
      printNewline();
      for (const auto *Child : BQ->getChildren())
         visit(Child);
      printNewline();
      print("</blockquote>");
      printNewline();
   }

   void visitList(const List *BL) {
      print(BL->isOrdered() ? "<ol>" : "<ul>");
      indent();
      printNewline();
      for (const auto *Child : BL->getChildren())
         visit(Child);
      dedent();
      print(BL->isOrdered() ? "</ol>" : "</ul>");
      printNewline();
   }

   void visitItem(const Item *I) {
      print("<li>");
      indent();
      printNewline();
      for (const auto *N : I->getChildren())
         visit(N);
      dedent();
      print("</li>");
      printNewline();
   }

   void visitCodeBlock(const CodeBlock *CB) {
      print("\\code");
      printNewline();
      print(CB->getLiteralContent());
      printNewline();
      print("\\endcode");
   }

   void visitCode(const Code *C) {
      print("<code>");
      print(C->getLiteralContent());
      print("</code>");
   }

   void visitHTML(const HTML *H) {
      print(H->getLiteralContent());
   }

   void visitInlineHTML(const InlineHTML *IH) {
      print(IH->getLiteralContent());
   }

   void visitSoftBreak(const SoftBreak *SB) {
      printNewline();
   }

   void visitLineBreak(const LineBreak *LB) {
      print("<br/>");
      printNewline();
   }

   void visitLink(const Link *L) {
      SmallString<32> Tag;
      llvm::raw_svector_ostream S(Tag);
      S << "<a href=\"" << L->getDestination() << "\">";
      print(S.str());
      for (const auto *Child : L->getChildren())
         visit(Child);
      print("</a>");
   }

   void visitImage(const Image *I) {
      SmallString<64> Tag;
      llvm::raw_svector_ostream S(Tag);
      S << "<img src=\"" << I->getDestination() << "\"";
      if (I->hasTitle())
         S << " title=\"" << I->getTitle() << "\"";
      if (I->getChildren().size()) {
         S << " alt=\"";
         for (const auto *Child : I->getChildren())
            printInlinesUnder(Child, S);
         S << "\"";
      }
      S << "/>";
      print(S.str());
   }

   void visitParagraph(const Paragraph *P) {
      for (const auto *Child : P->getChildren())
         visit(Child);
      printNewline();
   }

   void visitEmphasis(const Emphasis *E) {
      print("<em>");
      for (const auto *Child : E->getChildren())
         visit(Child);
      print("</em>");
   }

   void visitStrong(const Strong *E) {
      print("<em>");
      for (const auto *Child : E->getChildren())
         visit(Child);
      print("</em>");
   }

   void visitHRule(const HRule *HR) {
      print("<hr/>");
      printNewline();
   }

   void visitHeader(const Header *H) {
      llvm::SmallString<4> Tag;
      llvm::raw_svector_ostream TagStream(Tag);
      TagStream << "<h" << H->getLevel() << ">";
      print(TagStream.str());
      for (const auto *Child : H->getChildren())
         visit(Child);
      llvm::SmallString<5> EndTag;
      llvm::raw_svector_ostream EndTagStream(EndTag);
      EndTagStream << "</h" << H->getLevel() << ">";
      print(EndTagStream.str());
      printNewline();
   }

   void visitText(const Text *T) {
      print(T->getLiteralContent());
   }

   void visitPrivateExtension(const PrivateExtension *PE) {
      llvm_unreachable("Can't directly print Doxygen for a Swift Markup PrivateExtension");
   }

   void visitParamField(const ParamField *PF) {
      print("\\param ");
      print(PF->getName());
      print(" ");
      if (PF->isClosureParameter()) {
         printNestedParamField(PF);
      } else {
         for (const auto *Child : PF->getChildren()) {
            visit(Child);
         }
      }
      printNewline();
   }

   void visitReturnField(const ReturnsField *RF) {
      print("\\returns ");
      for (const auto *Child : RF->getChildren())
         visit(Child);
      printNewline();
   }

   void visitThrowField(const ThrowsField *TF) {
      print("\\param error ");
      for (const auto *Child : TF->getChildren())
         visit(Child);
      printNewline();
   }

#define MARKUP_SIMPLE_FIELD(Id, Keyword, XMLKind) \
  void visit##Id(const Id *Field) { \
    print(#Keyword); \
    print(":"); \
    printNewline(); \
    for (const auto *Child : Field->getChildren()) \
      visit(Child); \
  }
#include "polarphp/markup/SimpleFieldsDefs.h"

   ~DoxygenConverter() override {
      if (IsEmptyComment || !IsFreshLine)
         printUncommentedNewline();
   }
};

void ide::getDocumentationCommentAsDoxygen(const DocComment *DC,
                                           raw_ostream &OS) {
   DoxygenConverter Converter(OS);

   auto Brief = DC->getBrief();
   if (Brief.hasValue()) {
      Converter.visit(Brief.getValue());
   }

   for (const auto *N : DC->getBodyNodes()) {
      if (const auto *P = dyn_cast<Paragraph>(N)) {
         Converter.visit(P);
         continue;
      }
      Converter.visit(N);
   }

   for (const auto PF : DC->getParamFields()) {
      Converter.visit(PF);
   }

   auto TF = DC->getThrowsField();
   if (TF.hasValue()) {
      Converter.printNewline();
      Converter.visit(TF.getValue());
   }

   auto RF = DC->getReturnsField();
   if (RF.hasValue()) {
      Converter.printNewline();
      Converter.visit(RF.getValue());
   }
}

